package br.com.ordersys.web.jobs;

import java.io.ByteArrayInputStream;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import javax.mail.Session;

import org.springframework.stereotype.Service;

import br.com.ordersys.framework.db.DBSessionManager;
import br.com.ordersys.framework.db.IDBSession;
import br.com.ordersys.framework.db.IValueObject;
import br.com.ordersys.framework.db.Query;
import br.com.ordersys.framework.db.Query.RowIterator;
import br.com.ordersys.framework.db.tx.ITransactionalBlock;
import br.com.ordersys.framework.scheduler.IScheduledJob;
import br.com.ordersys.framework.scheduler.JobInfo;
import br.com.ordersys.framework.scheduler.JobRunningContext;
import br.com.ordersys.framework.scheduler.JobTrigger.IntervalType;
import br.com.ordersys.framework.scheduler.JobTrigger.Type;
import br.com.ordersys.framework.utils.DateUtils;
import br.com.ordersys.framework.utils.ReversibleEncryption;
import br.com.ordersys.web.data.ContaSMTPVO;
import br.com.ordersys.web.data.FilaEmailVO;
import br.com.ordersys.web.mail.MailMessage;
import br.com.ordersys.web.mail.MailSender;
import br.com.ordersys.web.mail.MailSender.TransportType;

@Service("job.mail.sender")
@JobInfo(description = "OrderSys mail sender", id = "job.mail.sender", intervalType = IntervalType.SECONDS, type = Type.INTERVAL_TYPE, interval = 30)
public class MailSenderJOB implements IScheduledJob {

	@Override
	public void doWork(JobRunningContext jrc) {
		IDBSession dbSession = null;
		try {
			dbSession = DBSessionManager.open();
			final Map<Integer, Session> sessionByStmpID = new HashMap<Integer, Session>();

			Query<FilaEmailVO> qMessages = dbSession.buildQuery(FilaEmailVO.class).setCriteria("this.STATUSENVIO IN(:p1,:p2)").setOrderBy("this.DHINCLUSAO");
			qMessages.params(FilaEmailVO.STATUS_ENVIO.SEND_PENDING.get(), FilaEmailVO.STATUS_ENVIO.SEND_ERROR.get());

			final ExecutionInfo ei = new ExecutionInfo();
			
			qMessages.iterate(new RowIterator<FilaEmailVO>() {
				@Override
				public void next(FilaEmailVO vo) {
					sendMailMessage(vo, sessionByStmpID, ei);
				}
			});
			
			jrc.addLogMessage("Total de mensagens enviadas com sucesso: " + ei.totalOfSentMessages);
			jrc.addLogMessage("Total de mensagens que falharam: " + ei.totalOfFailMessages);
		} finally {
			DBSessionManager.close(dbSession);
		}
	}

	private boolean checkSMTPQuota(ContaSMTPVO smtpVO) {
		//validação da cota diária
		if (smtpVO.asInt("COTAPORDIA") > 0) {
			if (smtpVO.asTimestamp("DTCOTADIA") != null) {
				Timestamp today = DateUtils.truncByDay(new Timestamp(System.currentTimeMillis()));

				if (today.equals(smtpVO.asTimestamp("DTCOTADIA"))) {
					if (smtpVO.asInt("QTDMSGDIA") + 1 > smtpVO.asInt("COTAPORDIA")) {
						return false; //cota do dia já atingida
					}
				}
			}
		}

		//validação da cota horária
		if (smtpVO.asInt("COTAPORHORA") > 0) {
			if (smtpVO.asTimestamp("DHCOTAHORA") != null) {
				Timestamp thisHour = DateUtils.truncByHour(new Timestamp(System.currentTimeMillis()));

				if (thisHour.equals(smtpVO.asTimestamp("DHCOTAHORA"))) {
					if (smtpVO.asInt("QTDMSGHORA") + 1 > smtpVO.asInt("COTAPORHORA")) {
						return false; //cota da hora já atingida
					}
				}
			}
		}

		return true;
	}

	protected void sendMailMessage(final FilaEmailVO msgVO, Map<Integer, Session> sessionByStmpID, final ExecutionInfo ei) {
		IDBSession dbSession = DBSessionManager.getCurrentSession();

		final ContaSMTPVO smtpVO = dbSession.findByPK(ContaSMTPVO.class, msgVO.asInt("IDCONTASMTP"));

		if(!checkSMTPQuota(smtpVO)){
			return;
		}
		
		Session jvMailSession = sessionByStmpID.get(smtpVO.asInt("IDCONTASMTP"));
		if (jvMailSession == null) {
			TransportType tType = getTransportType(smtpVO);

			jvMailSession = MailSender.buildSession(smtpVO.asString("HOST"), smtpVO.asInt("PORTA"), smtpVO.asString("EMAIL"), ReversibleEncryption.decrypt(smtpVO.asString("SENHA")), tType);
		}

		MailMessage mm = new MailMessage();

		mm.setFrom(smtpVO.asString("EMAIL"));
		mm.setMimeType(msgVO.asString("MIMETYPE"));
		mm.setSubject(msgVO.asString("ASSUNTO"));

		char[] body = msgVO.asClob("CORPO");
		mm.setMessageText(String.valueOf(body == null ? new char[] {} : body));

		mm.addRecipientTo(msgVO.asString("EMAILDEST")); // destinatario principal
		mm.setReplyTo(msgVO.asString("EMAILRESPOSTA"));

		Collection<IValueObject> outrosDestVO = dbSession.buildQuery("T01EMAILCC").execute("this.IDMSG = :p1", msgVO.asBigDecimal("IDMSG"));

		for (IValueObject ccVO : outrosDestVO) {
			mm.addRecipientTo(ccVO.asString("EMAILCC")); // destinatarios secundarios
		}

		Collection<IValueObject> anexosVO = dbSession.buildQuery("T01ANEXOEMAIL").execute("this.IDMSG = :p1", msgVO.asBigDecimal("IDMSG"));

		for (IValueObject anexoVO : anexosVO) {
			mm.addAttachment(anexoVO.asString("NOME"), new ByteArrayInputStream(anexoVO.asBlob("CONTEUDO")), anexoVO.asString("MIMETYPE"));
		}

		final MailSender.SendResult result = MailSender.send(mm, jvMailSession);

		dbSession.executeOnTX(new ITransactionalBlock<Object>() {
			@Override
			public Object execute() {
				Timestamp now = new Timestamp(System.currentTimeMillis());

				if (msgVO.asTimestamp("DHPRITENTATIVA") == null) {
					msgVO.set("DHPRITENTATIVA", now);
				}

				int qtd = msgVO.asInt("QTDTENTATIVAS");

				msgVO.set("DHULTTENTATIVA", now);
				msgVO.set("QTDTENTATIVAS", qtd + 1);

				if (result.getStatus() == MailSender.SendResult.Status.SUCESS) {
					msgVO.set("STATUSENVIO", FilaEmailVO.STATUS_ENVIO.SEND_SUCESS.get());
					updateSMTPQuota(smtpVO);
					
					ei.totalOfSentMessages++;
				} else {
					// TODO devo testar a qtd. maxima de tentativas de envio e setar como erro permanente
					msgVO.set("STATUSENVIO", FilaEmailVO.STATUS_ENVIO.SEND_ERROR.get());
					msgVO.set("ULTMSGERRO", result.getErrorMessage());
					ei.totalOfFailMessages++;
				}

				msgVO.save();
				
				return null;
			}
		});

	}

	protected void updateSMTPQuota(ContaSMTPVO smtpVO) {
		Timestamp now = new Timestamp(System.currentTimeMillis());
		
		if (smtpVO.asInt("COTAPORDIA") > 0) {
			Timestamp today = DateUtils.truncByDay(now);
			int cur = smtpVO.asInt("QTDMSGDIA");
			
			if (smtpVO.asTimestamp("DTCOTADIA") == null || !smtpVO.asTimestamp("DTCOTADIA").equals(today)) {
				cur = 0;
			}
			
			smtpVO.set("QTDMSGDIA",cur+1);
			smtpVO.set("DTCOTADIA",today);
		}
		
		if (smtpVO.asInt("COTAPORHORA") > 0) {
			Timestamp thisHour = DateUtils.truncByHour(now);
			int cur = smtpVO.asInt("QTDMSGHORA");
			
			if (smtpVO.asTimestamp("DHCOTAHORA") == null || !smtpVO.asTimestamp("DHCOTAHORA").equals(thisHour)) {
				cur = 0;
			}
			
			smtpVO.set("QTDMSGHORA",cur+1);
			smtpVO.set("DHCOTAHORA",thisHour);
		}
		
		smtpVO.save();
	}

	private TransportType getTransportType(ContaSMTPVO smtpVO) {
		String tipo = smtpVO.asString("TIPOSEGURANCA");

		if (ContaSMTPVO.SECURITY_TYPE.SSL.get().equals(tipo)) {
			return TransportType.SSL;
		} else if (ContaSMTPVO.SECURITY_TYPE.TLS.get().equals(tipo)) {
			return TransportType.TLS;
		} else {
			throw new IllegalStateException("Tipo de segurança não suportado: " + tipo);
		}
	}
	
	private class ExecutionInfo{
		int totalOfSentMessages;
		int totalOfFailMessages;
	}

}
